﻿namespace NTLib.WPF.Interactions
{
    using Application;
    using Core.Log;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;

    /// <summary>
    /// Base class used to create dynamic interaction on WPF xaml
    /// </summary>
    /// <typeparam name="TDependencyObject">The type of the dependency object.</typeparam>
    /// <seealso cref="System.Windows.DependencyObject" />
    /// <seealso cref="NTLib.WPF.Interactions.IInteraction" />
    /// <seealso cref="System.ComponentModel.INotifyPropertyChanged" />
    public abstract class BaseInteraction<TDependencyObject> : DependencyObject, IInteraction, INotifyPropertyChanged
        where TDependencyObject : DependencyObject
    {
        #region Fields

        private TDependencyObject _attacheObject = null;
        internal IInteraction _parent;
        private object _dataContext;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the data context.
        /// </summary>
        /// <remarks>
        /// Every binding that will followed must Specify this property has source because only FrameworkElement inherite each other from the data context.
        /// </remarks>
        public object DataContext
        {
            get
            {
                var localValue = this._dataContext;
                if (localValue == null)
                    localValue = GetParentContext();
                return localValue;
            }
            set
            {
                this._dataContext = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Gets the attached object.
        /// </summary>
        /// <value>
        /// The attached object.
        /// </value>
        protected TDependencyObject AttachedObject
        {
            get { return this._attacheObject; }
        }

        /// <summary>
        /// Gets the parent.
        /// </summary>
        protected IInteraction Parent
        {
            get { return this._parent; }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Methods

        /// <summary>
        /// Attacheds the specified object.
        /// </summary>
        /// <param name="obj">The object.</param>
        public void Attached(DependencyObject obj)
        {
            this._attacheObject = obj as TDependencyObject;
            try
            {
                OnAttached(obj);
            }
            catch (Exception ex)
            {
                NTLog.Instance.RecordError(ex, nameof(BaseInteraction<TDependencyObject>));
            }
            this.OnPropertyChanged(nameof(DataContext));
        }

        /// <summary>
        /// Dettacheds the specified object.
        /// </summary>
        public void Detached()
        {
            this._attacheObject = null;
            try
            {
                OnDetached();
            }
            catch (Exception ex)
            {
                NTLog.Instance.RecordError(ex, nameof(BaseInteraction<TDependencyObject>));
            }
            this.OnPropertyChanged(nameof(DataContext));
        }

        /// <summary>
        /// Called when [detached].
        /// </summary>
        /// <param name="obj">The object.</param>
        protected virtual void OnDetached() { }

        /// <summary>
        /// Called when attached.
        /// </summary>
        /// <param name="obj">The object.</param>
        protected virtual void OnAttached(DependencyObject obj) { }

        /// <summary>
        /// Gets the parent context.
        /// </summary>
        /// <returns></returns>
        private object GetParentContext()
        {
            if (this.Parent != null)
                return this.Parent.DataContext;
            if (this.AttachedObject is FrameworkElement)
                return (this.AttachedObject as FrameworkElement).DataContext;
            return this.AttachedObject;
        }

        /// <summary>
        /// Called when [property changed].
        /// </summary>
        /// <param name="caller">The caller.</param>
        protected void OnPropertyChanged([CallerMemberName]string caller = null)
        {
            DispatchHelper.Instance.DispatchExec(() =>
            {
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(caller));
            });
        }

        #endregion
    }

    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="System.Windows.DependencyObject" />
    /// <seealso cref="NTLib.WPF.Interactions.IInteraction" />
    public abstract class BaseInteraction : BaseInteraction<DependencyObject>
    {
    }
}
